﻿/*
PegEdit Application for Windows
A peg plotting code outputting application.

Karim Sultan, October 30 2009.
karimsultan@hotmail.com

This is free software.  Just be fair and adhere to the LGPL 2.1 license,
which says you can use it for your own purposes but can't violate copyright.

Peggy2 is by Windell Oskay of Evil Mad Scientist Laboratories.
http://www.evilmadscientist.com

PegEdit lets you plot your greyscale LED pictures for Peggy2.

It can generate either the image array of raw data, or the entire
Arduino sketch code (just paste and upload).

Sketches will require the Peggy2Frame class, most likely available
from the same place you obtained this application.

Enjoy!

REVISIONS
=========
 Version 0.1 
 October  30 2009, Karim Sultan
 * Pre-Release (Alpha)
 * Untested and unfinished but functional
 * Released under LGPL

 
 Version 0.2
 November 3 2009, Karim Sultan
 * Added local image repository (XML database)
 * Added support for Save, Load and Delete
 * Added images list of saved images
 * Added support for Planar images (4 planes x 25 rows x 4 bytes)
 * Added sketch output for Planar
 * Added Right Mouse button support for zeroing cells (erasing)
 * Added image shifting (left, right, up, down) in translation mode
 * Added image rotating (90CW, 90CCW, 180) in rotate mode
 * Added image flipping (horizontal, vertical) in mirror mode
 * Added dirty flag check on Load image
 * Added dirty flag check on application exit
 * Prepped engine for animations (next version)
 * Abstracted image management to PegImage class
 * Added PegDB class to handle the XML file with image data and preferences
 * Moved palette management to LEDPalette class
 * Pallettes now load from LEDPalette object on the fly, making 
   customization of LED Sets very simple (add your own for different colors)
 * Changed Marquis / added default image to image database
 * Images now save as Base64; you can decode them externally if desired
 * Added nifty "Peg Edit" image as the new "marquis" (you have to load it though)
 * Added optimized double buffering - no more flicker!
 * Added support for saving / loading user preferences (like LED set, etc...)
 * Fixed accidental error causing v0.1 to not plot more than once
   when mouse was down... annoying!
 * Fixed low level shades on some palette which were too dark to show up
 * Fixed several minor (trivial/cosmetic) bugs
 * Changed output of RAW image to y rows with x columns (to read easier)
 * Corrected a swapped row,column index to image array in PegEdit
 * Corrected same swapped row,column index in Peggy2Frame
 * NOTE: This may cause backwards compatibility issues but hopefully
   the impact is trivial to non-existent. (It was an alpha...)
 * Removed alpha banner - app is now a stable beta
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace PegEdit
{
   public partial class Form1 : Form
   {
      // Members
      private const string TITLE = "PegEdit";
      private const string VERSION = "v0.2";

      private const int PALETTE_WIDTH = 30;
      private const int PALETTE_HEIGHT = 25;

      private const int CANVAS_WIDTH = 500;
      private const int CANVAS_HEIGHT = 500;

      // Image database
      private const string IMAGE_DB = "pegedit_images.peg";

      // Image data buffer
      //private byte[,] image = new byte[25, 25];
      private PegImage image = new PegImage();

      // The active palette
      private LEDPalette palette = new LEDPalette();

      // The application database
      private PegDB pegdb = new PegDB();

      // Flag for it image is dirty and requires save
      private bool flagImageDirty = false;


      public Form1()
      {
         InitializeComponent();
      }


      private void DrawPalette(Graphics gfx)
      {
         // Draw the palette
         int w = PALETTE_WIDTH;
         int h = PALETTE_HEIGHT;

         for (int row = 0; row < 2; row++)
            for (int col = 0; col < 8; col++)
            {
               Brush palBrush = new System.Drawing.SolidBrush(palette.GetColorAt((row * 8) + col));
               gfx.FillRectangle(palBrush, 515 + (col * w), 10 + (row * h), w, h);
            }

         // Draw frame around selected palette
         Pen palPen = new System.Drawing.Pen(Color.Gold);
         palPen.Width = 2;
         if (palette.ColorIndex < 8)
            gfx.DrawRectangle(palPen, 515 + (palette.ColorIndex * w), 10, w, h);
         else
            gfx.DrawRectangle(palPen, 515 + ((palette.ColorIndex - 8) * w), 10 + h, w, h);
      }


      private void DrawPegs(Graphics gfx)
      {
         // Draw canvas.  Pegs are actually 496x496
         Brush canvasBrush = new System.Drawing.SolidBrush(Color.Black);
         gfx.FillRectangle(canvasBrush, 0, 0, 500, 500);



         // Draw pegs
         for (int x = 0; x < 25; x++)
            for (int y = 0; y < 25; y++)
            {
               Pen pegPen = new Pen(Color.DimGray);

               gfx.DrawEllipse(pegPen, x * 20, y * 20, 18, 18);

               Brush pegBrush = new System.Drawing.SolidBrush(palette.GetColorAt(image.GetColorIndex(x, y)));
               gfx.FillEllipse(pegBrush, x * 20, y * 20, 18, 18);

            }
      }



      /// <summary>
      ///  Repaints the screen
      /// </summary>
      /// <param name="paintEvnt">The form PaintEventArgs.</param>
      protected override void OnPaint(PaintEventArgs paintEvnt)
      {
         // Get the graphics object 
         Graphics gfx = paintEvnt.Graphics;

         DrawPegs(gfx);
         DrawPalette(gfx);

      }


      // Checks if image has changed and allows user to abort to save
      private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
		{
         if (flagImageDirty)
            if(MessageBox.Show("Do you want to quit without saving?", 
               "Quit Without Saving?", 
               MessageBoxButtons.OKCancel) == DialogResult.Cancel)
					e.Cancel = true;
		}



      private void Form1_Load(object sender, EventArgs e)
      {
         // This critical piece of code enables .NET 2.0 built-in double buffering.
         // It's very secretive but makes a HUGE difference. Don't believe me?
         // Then comment out this line and try the program...
         this.SetStyle(ControlStyles.UserPaint |
            ControlStyles.AllPaintingInWmPaint |
            ControlStyles.OptimizedDoubleBuffer, true);

         // Set text box intro
         tbCode.Text =
            TITLE + " " + VERSION + "\r\n" +
            "October, 2009 by Karim Sultan\r\n" +
            "(karimsultan@hotmail.com)\r\n" +
            "Released under LGPL 2.1.  This is " +
            "free software.\r\n" +
            "\r\n" +
            "Sketches require the Peggy2Frame class " +
            "(which supports 16 greyscale " +
            "level ouput on a Peggy2).\r\n";

         tbCode.Select(0, 0);

         // Set default selected greylevel to max
         palette.ColorIndex = 15;

         // Start with Blue LEDs, my perogative as that's what I built! :)
         string[] names = palette.GetAllLedSetNames();
         for (int i = 0; i < names.Length; i++)
            listLED.Items.Add(names[i]);

         // Use the property one, unless we can't load it
         try
         {
            string value = pegdb.LoadProperty("Palette");
            listLED.Text = value;
            palette.SetPaletteByName(value);
         }
         catch (Exception ex)
         {
            listLED.SelectedIndex = 1;
         }

         PopulateImageList();

         // Load the marquis
         if (pegdb.ImageExists("Peg Edit"))
            pegdb.LoadImage(image, "Peg Edit");
      }


      private void PopulateImageList()
      {
         try
         {
            // Retrieve images list
            string[] imagelist = pegdb.ListImages();

            for (int i = 0; i < imagelist.Length; i++)
               lbImages.Items.Add(imagelist[i]);
         }
         catch (Exception ex)
         {
            // Do nothing for now
         }
      }


      private void btnClear_Click(object sender, EventArgs e)
      {
         image.Clear();
         flagImageDirty = false;
         DrawPegs(this.CreateGraphics());
         tbImageName.Text = "";
         UpdateCode();
      }



      private void listLED_SelectedIndexChanged(object sender, EventArgs e)
      {
         palette.SetPaletteByName(listLED.Text);
         pegdb.SaveProperty("Palette", listLED.Text);
         this.Refresh();
      }



      // Handles any captured mouse event
      private void MyMouseEvent(MouseEventArgs e)
      {
         int x = e.X;
         int y = e.Y;

         // Is this click on the canvas?
         Rectangle rectCanvas = new Rectangle(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
         Rectangle rectPalette = new Rectangle(515, 10, PALETTE_WIDTH * 8, PALETTE_HEIGHT * 2);
         Point p = new Point(x, y);

         if (rectCanvas.Contains(p))
         {
            // If left, draw; if right, clear.
            byte g = (byte)palette.ColorIndex;
            if (e.Button == MouseButtons.Right)
               g = 0;

            HandlePeg(x, y, g);
         }
         else if (rectPalette.Contains(p))
            HandlePalette(x, y);
      }


      // Calls base class and then custom handler on mouse click
      protected override void OnMouseDown(MouseEventArgs e)
      {
         base.OnMouseDown(e);
         MyMouseEvent(e);
      }


      // Calls base class and then custom handler on mouse move + click
      protected override void OnMouseMove(MouseEventArgs e)
      {
         base.OnMouseMove(e);
         if (!(e.Button == MouseButtons.None))
            MyMouseEvent(e);
      }


      // Manages peg selection on pegboard canvas
      void HandlePeg(int x, int y, byte grey)
      {
         // Normalized x,y to {(0,0) .. (24,24)}
         int nx, ny;

         // Transform (origin already (0,0) so no translation necessary
         nx = x / (CANVAS_WIDTH / 25);
         ny = y / (CANVAS_HEIGHT / 25);

         // Rare case where x or y can be 25 - due to rectangle.contains method
         // not actually be exclusive with outer border
         if (nx > 24) nx = 24;
         if (ny > 24) ny = 24;

         // Plot point in virtual image
         image.SetPoint(nx, ny, grey);
         flagImageDirty = true;
         UpdateCode();

         // Draw the peg
         // This is much faster than redrawing entire canvas
         Graphics gfx = this.CreateGraphics();
         Brush pegBrush = new System.Drawing.SolidBrush(palette.GetColorAt(image.GetColorIndex(nx, ny)));
         gfx.FillEllipse(pegBrush, nx * 20, ny * 20, 18, 18);
      }


      // Manages palette selection
      void HandlePalette(int x, int y)
      {
         // Normalized x,y to {(0,0) .. (7,1)}
         int nx, ny;

         // Translate to offset from 0,0 instead of palette origin
         nx = (x - 515) / PALETTE_WIDTH;
         ny = ((y - 10) / PALETTE_HEIGHT);

         palette.ColorIndex = (byte)((ny * 8) + nx);

         DrawPalette(this.CreateGraphics());
      }


      // Renders image as ascii C++ code
      // Note for C#: you must change the array declaraction from [][] to [,]
      void UpdateCode()
      {
         int x, y;
         String code = "";

         // Preamble
         if (cbSketch.Checked)
         {
            code +=
              "/*\r\n" +
              "Generated by PegEdit for Peggy2 boards.\r\n" +
              "PegEdit is by Karim Sultan (karimsultan@hotmail.com).\r\n" +
              "Peggy 2 is by Windell Oskay of www.evilmadscientist.com.\r\n" +
              "*/\r\n\r\n" +
              "#include <Peggy2Frame.h>\r\n\r\n";
         }

         if (rbPlanar.Checked)
            code += image.ToString(PegImage.FORMAT_TYPE.IMAGE_PLANAR);
         else if (rbImageRaw.Checked)
            code += image.ToString(PegImage.FORMAT_TYPE.IMAGE_RAW);

         // End
         code += "};\r\n\r\n";

         // Wrap-up
         if (cbSketch.Checked)
         {
            code +=
            "Peggy2Frame screen;\r\n\r\n" +
            "void setup(){}\r\n\r\n" +
            "void loop()\r\n" +
            "{\r\n";

            // Select apropriate method (raw, planar, etc...)
            if (rbImageRaw.Checked)
               code += "  screen.DisplayImageRAW(image);\r\n";
            else if (rbPlanar.Checked)
               code += "  screen.DisplayImagePlanar(image);\r\n";

            code += "  while (true) screen.Refresh();\r\n}\r\n\r\n";
         }

         // Display it
         tbCode.Text = code;
      }

      private void btnCopy_Click(object sender, EventArgs e)
      {
         Clipboard.SetText(tbCode.Text);
      }


      private void rbSketch_CheckedChanged(object sender, EventArgs e)
      {
         UpdateCode();
      }


      private void rbImageRaw_CheckedChanged(object sender, EventArgs e)
      {
         UpdateCode();
      }


      private void btnSave_Click(object sender, EventArgs e)
      {
         // Validation
         if (tbImageName.Text == "")
            return;

         if (pegdb.ImageExists(tbImageName.Text))
         {
            if (MessageBox.Show("Do you want to overwrite the existing the " +
               "following image: \n\n\"" + tbImageName.Text + "\"?",
               "Image Already Exists",
               MessageBoxButtons.YesNo,
               MessageBoxIcon.Question) == DialogResult.No)
               return;
         }

         try
         {
            pegdb.SaveImage(image, tbImageName.Text);
         }
         catch (Exception ex)
         {
            // Report error
            MessageBox.Show("[Sace:Error] The image save operation failed:\n\n" + ex.Message,
               "Error", MessageBoxButtons.OK,
               MessageBoxIcon.Error);
            return;
         }

         // Add name to listbox if not there
         if (!lbImages.Items.Contains(tbImageName.Text))
            lbImages.Items.Add(tbImageName.Text);

         tbCode.Text = "Image data has been saved as \"" + tbImageName.Text + "\".";
      }


      private void btnLoad_Click(object sender, EventArgs e)
      {
         // Validate
         if (tbImageName.Text == "")
            return;

         // Check if image was changed
         if (flagImageDirty)
         {
            if (MessageBox.Show("You will lose any changes to your current image.",
               "Load Image", MessageBoxButtons.OKCancel,
               MessageBoxIcon.Question) == DialogResult.Cancel)
               return;
         }

         try
         {
            pegdb.LoadImage(image, tbImageName.Text);
         }
         catch (Exception ex)
         {
            // Report error
            MessageBox.Show("[Load:Error] The image load operation failed:\n\n" + ex.Message,
               "Error", MessageBoxButtons.OK,
               MessageBoxIcon.Error);

            return;
         }

         flagImageDirty = false;
         this.Refresh();
         tbCode.Text = "Loaded image data: \"" + tbImageName.Text + "\".";
      }


      // A delete operation is really a rewrite of the file, less the unwanted record.
      private void btnDelete_Click(object sender, EventArgs e)
      {
         // Validate
         if (tbImageName.Text == "")
            return;

         if (!pegdb.ImageExists(tbImageName.Text))
            return;

         // Prompt user
         if (MessageBox.Show("Are you sure you want to delete the " +
            "following saved image:\n\n\"" + tbImageName.Text + "\"?",
            "Delete Image",
            MessageBoxButtons.YesNo,
            MessageBoxIcon.Question) == DialogResult.No)
            return;

         try
         {
            pegdb.DeleteImage(tbImageName.Text);
         }
         catch (Exception ex)
         {
            // Report error
            MessageBox.Show("[Delete:Error] The image load operation failed:\n\n" + ex.Message,
               "Error", MessageBoxButtons.OK,
               MessageBoxIcon.Error);
            return;
         }

         // Remove it from the list
         tbCode.Text = "Image \"" + tbImageName.Text + "\" has been deleted.";
         lbImages.Items.Remove(tbImageName.Text);
         image.Clear();
         flagImageDirty = false;
         this.Refresh();
      }


      private void rbPlanar_CheckedChanged(object sender, EventArgs e)
      {
         UpdateCode();
      }


      private void lbImages_SelectedIndexChanged(object sender, EventArgs e)
      {
         if (lbImages.SelectedItem == null)
            tbImageName.Text = "";
         else
            tbImageName.Text = lbImages.SelectedItem.ToString();
      }



      // Moves image one row up with wrap
      private void btnTransformUp_Click(object sender, EventArgs e)
      {
         if (rbModeTranslate.Checked)
            image.ShiftUp();
         else if (rbModeMirror.Checked)
            image.FlipVertical();
         else if (rbModeRotate.Checked)
            image.Rotate180();

         UpdateCode();
         this.Refresh();
      }


      // Moves image one row down with wrap
      private void btnTransformDown_Click(object sender, EventArgs e)
      {
         if (rbModeTranslate.Checked)
            image.ShiftDown();
         else if (rbModeMirror.Checked)
            image.FlipVertical();
         else if (rbModeRotate.Checked)
            image.Rotate180();

         UpdateCode();
         this.Refresh();
      }

      private void btnTransformRight_Click(object sender, EventArgs e)
      {
         if (rbModeTranslate.Checked)
            image.ShiftRight();
         else if (rbModeMirror.Checked)
            image.FlipHorizontal();
         else if (rbModeRotate.Checked)
            image.RotateCW();

         UpdateCode();
         this.Refresh();
      }

      private void btnTransformLeft_Click(object sender, EventArgs e)
      {
         if (rbModeTranslate.Checked)
            image.ShiftLeft();
         else if (rbModeMirror.Checked)
            image.FlipHorizontal();
         else if (rbModeRotate.Checked)
            image.RotateCCW();

         UpdateCode();
         this.Refresh();
      }

      private void cbSketch_CheckedChanged(object sender, EventArgs e)
      {
         UpdateCode();
      }


   }  // Class
}  // Namespace
